import * as React from 'react';
const {
  PureComponent,
  PropTypes,
} = React;
import {findDOMNode} from 'react-dom';
import * as DropZone from 'react-dropzone';
import './uploader.scss';
import * as assign from 'object-assign';
import * as cx from 'classnames';
import {post} from '../../util/ajax';
import Cropper from 'react-cropper';

// @require 'cropperjs/dist/cropper.css';
// jest 不能支持这种写法
// import 'cropperjs/dist/cropper.css';
import 'blueimp-canvastoblob';
import * as find from 'lodash/find';
import {Mixin as FormsyMixin} from 'formsy-react';
import {decorate as Mixin} from 'react-mixin';
import {alert} from '../alert/alert';
import * as qs from 'qs';

const uploadFile = (file, reciever, params) => {
  const fd = new FormData();
  fd.append('file', file);

  const idx = reciever.indexOf('?');

  if (~idx && params) {
    params = assign(qs.parse(reciever.substring(idx + 1)), params);
    reciever = reciever.substring(0, idx) + '?' + qs.stringify(params);
  } else if (params) {
    reciever += '?' + qs.stringify(params);
  }

  return post(reciever, fd);
};

export const formatFileSize = (size, units = [' B', ' KB', ' M', ' G']) => {
  size = parseInt(size, 10) || 0;

  while (size > 1024 && units.length > 1) {
    size /= 1024;
    units.shift();
  }

  return size.toFixed(2) + units[0];
};

const valueToFile = (value) => {
  return value ? assign({
      state: 'init'
    }, typeof value == 'string' ? {
        value,
        url: value
    } : value) : null;
};

const notNull = item => item;


export default class Uploader extends PureComponent {
  static propTypes = {
    reciever: PropTypes.string.isRequired,
    limit: PropTypes.shape({
      width: PropTypes.number,
      height: PropTypes.number
    })
  };

  static defaultProps = {
    limit: null,
    accept: 'image/*',
    btnUploadClassName: 'btn-success',
    btnClassName: 'btn-info btn-sm',
    hideUploadButton: false
  };

  current = null;

  constructor(props) {
    super(props);

    const value = props.value;
    const multiple = props.multiple;

    this.state = {
      files: value ? (multiple ? value : [value]).map(item => valueToFile(item, props)).filter(notNull) : [],
      uploading: false,
      crop: this.buildCrop(props),
      compress: !!props.compress,
      compressOptions: props.compressOptions,
      maxSize: 0
    };

    this.updateFile = this.updateFile.bind(this);
    this.removeFile = this.removeFile.bind(this);
    this.handleDrop = this.handleDrop.bind(this);
    this.handleClick = this.handleClick.bind(this);
    this.handleClick = this.handleClick.bind(this);
    this.handleCrop = this.handleCrop.bind(this);
    this.handleDropRejected = this.handleDropRejected.bind(this);
    this.cancelCrop = this.cancelCrop.bind(this);
    this.handleImageLoaded = this.handleImageLoaded.bind(this);
    this.startUpload = this.startUpload.bind(this);
    this.stopUpload = this.stopUpload.bind(this);
    this.toggleUpload = this.toggleUpload.bind(this);
    this.tick = this.tick.bind(this);
    this.onChange = this.onChange.bind(this);
    this.addFiles = this.addFiles.bind(this);
    this.handleSelect = this.handleSelect.bind(this);
    this.handlePaste = this.handlePaste.bind(this);
  }

  componentWillReceiveProps(nextProps) {
    const props = this.props;

    if (props.value !== nextProps.value) {
      const value = nextProps.value;
      const multiple = nextProps.multiple;

      this.setState({
        files: value ? (multiple ? value : [value]).map(item => {
          let obj = valueToFile(item, nextProps);
          let org;

          if (obj && (org = find(this.state.files, item => item.value === obj.value))) {
            obj = assign({}, org, obj);
          }

          return obj;
        }).filter(notNull) : [],
      });
    }

    if (props.crop !== nextProps.crop) {
      this.setState({
        crop: this.buildCrop(nextProps)
      });
    }
  }

  buildCrop(props) {
    let crop = props.crop;

    if (crop && props.multiple) {
      alert('图片多选配置和裁剪配置冲突，目前不能二者都支持！');
      return null;
    }

    if (crop === true) {
      crop = {};
    }

    if (crop) {
      crop = assign({
        aspectRatio: 1, // 1 : 1
        guides: true,
        dragMode: 'move',
        viewMode: 1,
        rotatable: false,
        scalable: false
      }, crop);
    }

    return crop;
  }

  handleDropRejected(rejectedFiles, evt) {
    evt.type === 'change' && alert('您选择的文件类型不符已被过滤！');
  }

  startUpload() {
    if (this.state.uploading) {
      return;
    }

    this.setState({
      uploading: true,
      files: this.state.files.map(file => {
        if (file.state === 'error') {
          file.state = 'pending';
        }

        return file;
      })
    }, () => {
      const {onRequestLock} = this.props;

      onRequestLock && onRequestLock();
      this.tick();
    });
  }

  toggleUpload() {
    return this.state.uploading ? this.stopUpload() : this.startUpload();
  }

  stopUpload() {
    if (!this.state.uploading) {
      return;
    }

    this.setState({
      uploading: false
    });
  }

  tick() {
    if (this.current || !this.state.uploading) {return;}

    const file = find(this.state.files, item => item.state === 'pending');
    if (file) {
      this.current = file;

      file.state = 'uploading';
      this.setState({
        files: this.state.files.concat()
      }, () => this.updateFile(file, (error, file, obj) => {
        const files = this.state.files.concat();
        const idx = files.indexOf(file);

        if (!~idx) {
          return;
        }

        let newFile = file;

        if (error) {
          newFile.state = file.state !== 'uploading' ? file.state : 'error';
          newFile.error = error;

          if (!this.props.multiple && newFile.state === 'invalid') {
            files.splice(idx, 1);
            this.current = null;

            return this.setState({
              files: files,
              error: error
            }, this.tick);
          }

        } else {
          newFile = obj;
        }
        files.splice(idx, 1, newFile);
        this.current = null;
        this.setState({
          files: files
        }, this.tick);
      }));
    } else {
      this.setState({
        uploading: false
      }, () => {
        const {
          onRequestUnLock
        } = this.props;

        onRequestUnLock && onRequestUnLock();

        this.onChange();
      });
    }
  }

  removeFile(file, index) {
    const files = this.state.files.concat();

    files.splice(index, 1);

    this.setState({
      files: files
    }, () => {
      if (file.state === 'uploaded' || file.state === 'init') {
        this.onChange();
      }
    });
  }

  editImage(index) {
    const {
      multiple
    } = this.props;

    const files = this.state.files;

    this.setState({
      cropFile: {
        preview: files[index].url
      }
    });

  }

  onChange() {
    const {
      multiple,
      onChange
    } = this.props;

    const files = this.state.files.filter(file => file.state == 'uploaded' || file.state == 'init');

    onChange(multiple ? files : files[0]);
  }

  handleSelect() {
    this.refs.dropzone && this.refs.dropzone.open();
  }

  handleDrop(files) {
    const {multiple, crop} = this.props;

    if (crop && !multiple ) {
      this.props.onRequestLock && this.props.onRequestLock(null, '请选择放弃或者应用');

      return this.setState({
        cropFile: files[0]
      });
    }

    this.addFiles(files);
  }

  handlePaste(e) {
    const event = e.nativeEvent;
    const files = [];
    const items = event.clipboardData.items;

    [].slice.call(items).forEach(item => {
      let blob;

      if (item.kind !== 'file' || !(blob = item.getAsFile()) || !/^image/i.test(blob.type)) {
        return;
      }

      blob.preview = window.URL.createObjectURL(blob);
      files.push(blob);
    });

    this.handleDrop(files);
  }

  handleCrop() {
    this.refs.cropper.getCroppedCanvas().toBlob(file => {
      this.addFiles([file]);
      this.setState({cropFile: null});
    });
    this.props.onRequestUnLock && this.props.onRequestUnLock();
  }

  cancelCrop() {
    this.props.onRequestUnLock && this.props.onRequestUnLock();
    this.setState({cropFile: null}, this.onChange);
  }

  addFiles(files) {
    if (!files.length) {
      return;
    }

    const {multiple, maxLength, maxSize, accept} = this.props;
    let currentFiles = this.state.files;

    if (!multiple && currentFiles.length) {
      currentFiles = [];
    }

    const allowed = (multiple ? maxLength ? maxLength : (files.length + currentFiles.length) : 1) - currentFiles.length;
    const inputFiles = [];

    [].slice.call(files, 0, allowed).forEach(file => {
      if (maxSize && file.size > maxSize) {
        alert(`您选择的文件 ${file.name} 大小为 ${formatFileSize(file.size)} 超出了最大为 ${formatFileSize(maxSize)} 的限制，请重新选择`);
        return null;
      }

      file.state = 'pending';
      if (!file.preview || !file.url) {
        file.preview = window.URL.createObjectURL(file);
      }
      inputFiles.push(file);
    });

    if (!inputFiles.length) {
      return;
    }

    this.setState({
      error: null,
      files: currentFiles.concat(inputFiles)
    }, () => {
      const {
        autoUpload,
        onRequestLock
      } = this.props;

      if (autoUpload) {
        this.startUpload();
      } else if (onRequestLock) {
        onRequestLock(this.startUpload);
      }
    });
  }

  updateFile(file, cb) {
    const {limit} = this.props;

    if (!limit) {
      return this._upload(file, cb);
    }

    const image = new Image();
    image.onload = () => {
      const width = image.width;
      const height = image.height;
      let error = '';

      if (limit.width && limit.width != width || limit.height && limit.height != height) {
        error = `您选择的图片不符合尺寸要求, 请上传${sizeInfo(limit.width, limit.height)}的图片`;
      } else if (limit.maxWidth && limit.maxWidth < width || limit.maxHeight && limit.maxHeight < height) {
        error = `您选择的图片不符合尺寸要求, 请上传不要超过${sizeInfo(limit.maxWidth, limit.maxHeight)}的图片`;
      } else if (limit.minWidth && limit.minWidth > width || limit.minHeight && limit.minHeight > height) {
        error = `您选择的图片不符合尺寸要求, 请上传不要小于${sizeInfo(limit.minWidth, limit.minHeight)}的图片`;
      } else if (limit.aspectRatio && Math.abs((width / height) - limit.aspectRatio) > 0.01) {
        error = `您选择的图片不符合尺寸要求, 请上传尺寸比率为 ${limit.aspectRatioLabel || limit.aspectRatio} 的图片`
      }

      if (error) {
        file.state = 'invalid';
        cb(error, file);
      } else {
        this._upload(file, cb);
      }
    };
    image.src = file.preview || file.url;
  }

  _upload(file, cb) {
    let compressOptions = this.state.compressOptions;

    if (this.props.showCompressOptions) {
      compressOptions = assign({
        maxWidth: 800,
        maxHeight: 600
      }, compressOptions);
    }

    uploadFile(file, this.props.reciever, {compress: this.state.compress, compressOptions})
      .then(ret => {
        if (ret.status) {
          throw new Error(ret.msg || '上传失败, 请重试');
        }

        const obj = ret.data;
        obj.state = 'uploaded';
        obj.value = obj.value || obj.url;

        cb(null, file, obj);
      })
      .catch(error => cb(error.message || '上传失败，请重试', file))
  }

  handleClick() {
    this.refs.dropzone.open();
  }

  handleImageLoaded(index, e) {
    const imgDom = e.currentTarget;
    const img = new Image();
    img.onload = () => {
      img.onload = null;
      const files = this.state.files.concat();
      const file = files[index];

      if (!file) {
        return;
      }

      file.info = assign({}, file.info, {
        width: img.width,
        height: img.height
      });

      files.splice(index, 1, file);

      this.setState({
        files: files
      });
    };
    img.src = imgDom.src;
  }

  renderCompressOptions() {
    const showCompressOptions = this.props.showCompressOptions;

    if (!showCompressOptions) {
      return;
    }

    return (
      <div key="options" className="m-t">
        <label className="checkbox-inline i-switch">
          <input
            type="checkbox"
            checked={!!this.state.compress}
            onChange={e => this.setState({compress: e.currentTarget.checked})}
            disabled={this.props.disabled}
          /><i />
        </label>

        <span className="m-l-xs">开启缩放?</span>

        {this.state.compress && (
          <div className="inline">
            <input
              className="form-control w-xs inline m-l-xs m-r-xs"
              type="text"
              value={typeof this.state.compressOptions.maxWidth === 'undefined' ? 800 : this.state.compressOptions.maxWidth}
              onChange={e => this.setState({compressOptions: assign({}, this.state.compressOptions, {maxWidth: parseInt(e.currentTarget.value, 10) || 0})})}
              disabled={this.props.disabled}
            />

            <span className=" m-l-xs m-r-xs">X</span>

            <input
              className="form-control w-xs inline  m-l-xs m-r-xs"
              type="text"
              value={typeof this.state.compressOptions.maxHeight  === 'undefined' ? 600 : this.state.compressOptions.maxHeight}
              onChange={e => this.setState({compressOptions: assign({}, this.state.compressOptions, {maxHeight: parseInt(e.currentTarget.value, 10) || 0})})}
              disabled={this.props.disabled}
            />
          </div>
        )}
      </div>
    );
  }

  render() {
    const {
      placeholder,
      disabled,
      multiple,
      accept,
      maxLength,
      autoUpload,
      btnUploadClassName,
      btnClassName,
      hideUploadButton
    } = this.props;

    const {
      files,
      error,
      crop,
      uploading,
      cropFile
    } = this.state;

    const hasPending = files.some(file => file.state == 'pending');

    return (
      <div className="drop-zone-wrapper" tabIndex="-1" onPaste={this.handlePaste}>
        {cropFile ? (
          <div className="cropper-wrapper">
            <Cropper
              {...crop}
              ref="cropper"
              src={cropFile.preview}
            />
            <button
              type="button"
              className="btn-sm btn btn-link"
              onClick={this.handleCrop}
            >
              <i className="fa fa-2x fa-check text-warning" />
            </button>
            <button
              type="button"
              className="btn-sm btn btn-link"
              onClick={this.cancelCrop}
            >
              <i className="fa fa-2x fa-times text-white" />
            </button>
          </div>
        ) : (
          <DropZone
            key="drop-zone"
            className={cx('drop-zone', {disabled, 'has-files': files.length})}
            activeClassName="drop-zone-active"
            ref="dropzone"
            onDrop={this.handleDrop}
            onDropRejected={this.handleDropRejected}
            disableClick
            accept={accept}
            multiple={multiple}
          >
            {files && files.length ? (
            <div className="image-list clearfix">
              {files.map((file, key) => (
                <div
                  key={key}
                  className={cx('image-item pull-left', {
                    uploaded: file.state !== 'uploading',
                    invalid: file.state === 'error' || file.state == 'invalid'
                  })}
                >
                  <div className="img-wrapper"><img onLoad={this.handleImageLoaded.bind(this, key)} src={file.url || file.preview} alt={file.name} className="img-rounded" /></div>
                  {file.info ? [
                    <p key="1">{file.info.width} x {file.info.height}</p>,
                    file.info.len ? (<p key="2">{formatFileSize(file.info.len)}</p>) : null
                  ] : (<p>...</p>)}

                  {file.error ? (<p className="text-danger">{file.error}</p>) : null}

                  <div className="image-overlay">
                    {file.state === 'uploading' ? (<i className="fa fa-spinner fa-spin fa-2x fa-fw" />) : null}
                    {!disabled && file.state !== 'uploading' ? (<button onClick={this.removeFile.bind(this, file, key)} type="button" className={cx("close", {'crop-close' :!!crop})}><span>&times;</span></button>) : null}
                    {!!crop && !disabled && file.state !== 'uploading' ? (<button onClick={this.editImage.bind(this, key)} type="button" className="edit"><i className="fa fa-pencil"></i></button>) : null}
                  </div>
                </div>
              ))}

              {!disabled && multiple && (!maxLength || files.length < maxLength) || !multiple && !files.length ? (
                <label className={cx("image-add-btn", {disabled})} onClick={this.handleSelect}>
                  <i className="fa fa-plus fa-3x" />
                </label>
              ) : null}
            </div>
          ) : (<div className={error ? 'text-danger' : null}>{error || placeholder}
                <button type="button" className={cx('btn m-l-sm', btnClassName)} disabled={disabled} onClick={this.handleSelect}><i className="fa fa-cloud-upload" /> 选择文件</button>
          </div>)}
          </DropZone>
        )}

        {this.renderCompressOptions()}

        {!autoUpload && !hideUploadButton && files.length ? (
          <button className={cx('btn m-r-xs', btnUploadClassName)} disabled={!hasPending} onClick={this.toggleUpload}>
            {uploading ? '暂停上传' : '开始上传'}
          </button>
        ) : null}
      </div>
    );
  }
}

function sizeInfo(width, height) {
  if (!width) {
    return `高度${height}px`;
  } else if (!height) {
    return `宽度${width}px`;
  }

  return `尺寸（${width} x ${height}）`;
}

@Mixin(FormsyMixin)
export class FormsyInput extends PureComponent {

  constructor(props) {
    super(props);

    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(value) {
    this.setValue(value && value.url || value);
  }

  render() {
    return <Uploader {...this.props} onChange={this.handleChange} />
  }
}
